feat(acp): ACP Server with WebSocket transport#1260
Conversation
Add an ACP-compliant server endpoint at GET /acp that accepts WebSocket connections and speaks JSON-RPC 2.0 per the Agent Client Protocol spec. Implements: - WebSocket upgrade with Bearer token auth (OPENAB_ACP_AUTH_KEY) - initialize: capability negotiation - session/new: create OAB session mapped to internal channel - session/prompt: dispatch to agent via GatewayEvent, stream back AgentMessageChunk notifications from GatewayReply - session/cancel: placeholder for Phase 2 Architecture: ACP Client --WS JSON-RPC--> /acp endpoint --> GatewayEvent GatewayReply --> AcpReplyRegistry --> AgentMessageChunk notification --> Client Feature-gated behind 'acp' feature flag. Enable with: OPENAB_ACP_ENABLED=true cargo run --features acp Refs: ADR PR #1258
This comment has been minimized.
This comment has been minimized.
F1: Use subtle::ConstantTimeEq for auth token comparison (timing attack) F2: Remove duplicate AcpConfig/registry construction in serve() F3: Validate jsonrpc == "2.0" per spec (reject with -32600) F4: Defer reply channel creation to session/prompt (no dead _reply_rx) + Add tracing::debug for reply send failure (race condition visibility)
This comment has been minimized.
This comment has been minimized.
Critical fixes:
- 🔴 Reject concurrent prompts per session (-32001 'Session busy')
- 🔴 Abort prompt tasks on disconnect to prevent registry leaks
- 🔴 Clean up registry entries after prompt completes (not just on Done)
Important fixes:
- 🟡 Check event_tx.send() result — return JSON-RPC error if no agent connected
- 🟡 Remove unused agent-client-protocol dep (Phase 1 is manual JSON-RPC)
- 🟡 Add 'acp' to unified feature list in root Cargo.toml
- 🟡 Switch AcpReplyRegistry from tokio::sync::Mutex to std::sync::Mutex
(operations are CPU-bound, never held across .await)
Also:
- Rewrite acp_server.rs for clarity after multiple incremental fixes
- Add busy flag per session to track in-flight prompts
- Prompt handler now properly cleans up registry on all exit paths
|
CHANGES REQUESTED What This PR DoesAdds ACP (Agent Client Protocol) server support to OpenAB, enabling any ACP-compliant client (Zed, JetBrains, desktop apps, CLIs) to connect via WebSocket at How It WorksA new Findings
Finding Details🟡 F1: Duplicate AcpConfig::from_env() in serve()In the acp: adapters::acp_server::AcpConfig::from_env(),
acp_reply_registry: adapters::acp_server::AcpConfig::from_env()
.map(|_| adapters::acp_server::new_reply_registry()),Issues:
Fix: Extract once before the struct literal, matching the pattern in #[cfg(feature = "acp")]
let acp = adapters::acp_server::AcpConfig::from_env();
#[cfg(feature = "acp")]
let acp_reply_registry = acp.as_ref().map(|_| adapters::acp_server::new_reply_registry());Then reference the variables in the struct literal. This ensures the registry is only created when ACP is enabled and both fields share the same 🟢 F2–F6: All prior findings addressed
Baseline Check
What's Good (🟢)
CI Status
|
Summary
Implements Phase 1 MVP of the ACP Server per ADR #1258.
Any ACP-compliant client (Zed, JetBrains, desktop apps, web apps, CLIs) can now connect to OAB via WebSocket and interact with the multi-agent platform using the standard Agent Client Protocol.
Architecture
What's implemented (Phase 1)
GET /acpWebSocket upgrade endpointOPENAB_ACP_AUTH_KEYenv var)initialize— protocol version + capability negotiationsession/new— creates OAB session with internal channel mappingsession/prompt— converts prompt toGatewayEvent, dispatches through existing pipelineGatewayReplytranslated toAgentMessageChunknotifications (delta-based)session/cancel— placeholderAcpReplyRegistry) for routing replies back to the correct WebSocketacpfeature flagFiles changed
Cargo.tomlacpfeature flagcrates/openab-gateway/Cargo.tomlagent-client-protocoloptional dep +acpfeaturecrates/openab-gateway/src/adapters/acp_server.rscrates/openab-gateway/src/adapters/mod.rsacp_servermodulecrates/openab-gateway/src/lib.rsConfiguration
What's next (Phase 2+)
ToolCall,ToolCallUpdate)requestPermissionflow for dangerous operationssession/load— resume existing sessionsmodelparameterTesting
Refs: ADR PR #1258
cc @pahud